/** @file f_cistream.cpp
	@brief Circular filestream */

#include "file/f_cistream.h"

using namespace G3;

//==============================================================================
// Constructors & Destructors
//==============================================================================
G3::CISTREAM::CISTREAM (ulong reservedSpace): G3::STREAM () {
    caretPos = 0;
    TotalSize = 0;

	passedEOF = false;

    try {
        if (reservedSpace != 0)
            data.set_capacity (reservedSpace);
    } catch (...) {
        if (log)
            log->Report ("CISTREAM::CISTREAM: Failed to allocate %ld bytes of memory\n", reservedSpace);
    }
}

G3::CISTREAM::~CISTREAM () {
    data.clear ();
}

//==============================================================================
// Sets stream caret position
//
//		long Offset				File position to seek for
//
//		STREAM_SEEK_FLAG Flag
//			STREAM_BEGINNING	File position caret is reset to the beginning of
//								current stream buffer
//
//			STREAM_SET			Sets file position to Offset
//
//          STREAM_MOVE         Moves file position by Offset
//
//			STREAM_END			File position caret is moved to the end of the
//								stream
//
//	Returns true on success
//==============================================================================
bool G3::CISTREAM::Seek (long offset, STREAM_SEEK_FLAG flag) {
	switch (flag) {
		case G3::CISTREAM::STREAM_BEGINNING:
            caretPos = 0;
			passedEOF = false;
			return true;

		case G3::CISTREAM::STREAM_SET:
			if (offset >= (long) data.size ())
				passedEOF = true;

			caretPos = offset % ((long) data.size () - 1);
			return true;

        case G3::CISTREAM::STREAM_MOVE:
			if (caretPos + offset >= (long) data.size ())
				passedEOF = true;

			caretPos = (caretPos + offset) % ((long) data.size () - 1);
            return true;

		case G3::CISTREAM::STREAM_END:
			passedEOF = true;

			caretPos = Tell (G3::CISTREAM::STREAM_END);
			return true;

        default:
            break;
	}
	return false;
}

//==============================================================================
// Tells the stream caret position
//
//		STREAM_SEEK_FLAG Flag
//			STREAM_GET			Tells the CaretPosition of the stream
//
//			STREAM_END			Tells the size of the stream
//
//          STREAM_SIZE         Tells the amount of memory reserved for the stream
//
//	Returns the position in bytes or -1 if the input was invalid
//==============================================================================
long G3::CISTREAM::Tell (STREAM_SEEK_FLAG flag) {
	switch (flag) {
		case G3::STREAM::STREAM_GET:
			return caretPos;

		case G3::STREAM::STREAM_END:
            return ((long int) data.size ()) - 1;

        case G3::STREAM::STREAM_SIZE:
            return TotalSize;

        default:
            break;
	}
	return -1;
}

//==============================================================================
// Returns true if End Of File was encountered
//==============================================================================
bool G3::CISTREAM::EndOfFile () {
	return passedEOF;
}

//==============================================================================
// Flushes the whole buffer
//==============================================================================
bool G3::CISTREAM::Flush () {
    caretPos = 0;
	passedEOF = false;

    data.clear ();

    return true;
}

//==============================================================================
// Just sets the total size of the stream (in bytes)
//
//! \todo Doesn't work correctly - adds 0-bytes after each byte
//==============================================================================
void G3::CISTREAM::Resize (long size) {
    data.set_capacity (size);
}

//==============================================================================
// Reads data from the stream
//
//		void *Pointer			Pointer to a memory block where the data is stored
//
//		long Size				Number of bytes read from the stream
//
//  Note:: The array that pointer points to, needs to be at least the size of size + 1
//
//	Returns the number of bytes read
//==============================================================================
long G3::CISTREAM::Read (byte *pointer, long size, bool wrap_around) {
	byte *p = pointer;

	if (p == NULL) {
	    if (log)
            log->Report ("ERROR: CISTREAM::Read: Pointer argument is a NULL pointer..\n");
        return 0;
	}

	for (long i=0; i<size; i++) {
	    // Wrapping around?
	    if (!wrap_around) {
	        // No wrapping
            if (caretPos >= (long) data.size ()) {
                *p = 0;
                if (passedEOF)
                    return 0;
                passedEOF = true;
                break;
            } else
                *p = data.at (caretPos);
	    } else {
	        if (caretPos % ((long) data.size () - 1) == 0)
                printf ("<< At the beginning\n");

            *p = data.at (caretPos % ((long) data.size () - 1));
	    }

	    p++;
	    caretPos++;
	}

	return p - pointer;
}

//==============================================================================
// Reads a line from the stream. End of a line can be overridden..
//
//		string delimiters		End of line characters
//				"\r\n"			This is the default
//
//	Returns, what it was able to read from the stream until \r\n or EOS (End Of Stream)
//==============================================================================
std::string G3::CISTREAM::Readline (const std::string delimiters) {
	string line;
	byte c = 0;
	int i = 0;

	while (true) {
	    if (caretPos >= (long) data.size ())
            break;
        else
            c = data.at (caretPos);

		caretPos++;

		for (i=0; i<(int)delimiters.size (); i++) {
			if (c == delimiters [i]) {
				lastLineDelimiter = c;

				return line;
			}
		}

		line += c;
	}

	line += '\0';

	return line;
}

//==============================================================================
// Reads data from the stream and saves it into a file
//
//		string filename	        Name of the file to store the data in
//
//		long size				Number of bytes read from the stream
//				-1, 0			Reads the whole stream buffer
//
//		long position			Where to write into the file
//
//	Returns the number of bytes read
//==============================================================================
long G3::CISTREAM::ReadToFile (const std::string &filename, long size, long position) {
	long chunkSize = size;
	long blankSize = 0;
	long ret = 0, i = 0;
	byte blank = 0;

	Assert (position >= 0, "CISTREAM::ReadToFile: Position argument is invalid");

	if (filename.empty ())
        return 0;

    long fsize = GetFileSize (filename.c_str ());
    long bsize = Tell (G3::CISTREAM::STREAM_SIZE);

    if (bsize - position <= 0)
        return 0;

	if (size <= 0)
		chunkSize = bsize - position;
    else if (caretPos + size > (long) data.size ())
        chunkSize = (long) data.size () - caretPos;

	if (position > fsize) {
		blankSize = position - fsize;
		position = fsize - 1;
	}

	FILE *fo = fopen (filename.c_str (), "wb");
	if (!fo) {
		if (log)
			log->Report ("ERROR! CISTREAM::ReadToFile: Failed to open the file \"%s\"..\n", filename.c_str ());
		return 0;
	}

    // Seek to the position
	fseek (fo, position, SEEK_SET);

    // Align with blanks
    if (blankSize > 0)
        ret += fwrite (&blank, blankSize, 1, fo);

    for (i=0; i<chunkSize; i++) {
        // Something's fishy?
        if (caretPos >= (long) data.size ())
            break;

        ret += fwrite (&data.at (caretPos), sizeof (byte), 1, fo);
        caretPos++;
    }

    // Close it
	fclose (fo);

	return ret;
}

//==============================================================================
// Adds to the stream buffer
//
//		void *Pointer			Pointer to a memory block from where to buffer
//
//		long Size				Number of bytes to buffer
//
//	Returns true on success
//==============================================================================
long G3::CISTREAM::Buffer (byte *pointer, long size) {
	Assert (pointer, "CISTREAM::Buffer: Pointer argument is a NULL pointer");
	Assert (size > 0, "CISTREAM::Buffer: Size argument is invalid (<= 0)");

	byte *p = pointer;

	for (long i=0; i<size; i++) {
	    if (caretPos >= (long) data.size ())
            data.push_back (*p);
        else
            data.insert (data.begin () + caretPos, 1, *p);

        p++;
		caretPos++;
		TotalSize++;
	}

	return size;
}

//==============================================================================
// Buffers a file to the stream
//
//		string filename	        Name of the file to buffer
//
//		long size				Number of bytes to buffer
//				-1, 0			Buffers the whole file into the stream
//
//		long position			Where in the file should we start reading
//
//	Returns true on success
//==============================================================================
long G3::CISTREAM::BufferFromFile (const std::string &filename, long size, long position) {
	long chunkSize = size;
	long ret = 0, i = 0;
	byte b = 0;

	if (position < 0) {
		if (log)
			log->Report ("CISTREAM::BufferFromFile: Position argument is invalid (< 0)\n");
		return 0;
	}

	if (filename.empty ()) {
        if (log)
            log->Report ("CISTREAM::BufferFromFile: Empty filename\n");
        return 0;
	}

	long fsize = GetFileSize (filename.c_str ());

    if (fsize < 1) {
        if (log)
            log->Report ("CISTREAM::BufferFromFile: The file \"%s\" does not exist\n", filename.c_str ());
        return 0;
    }

    // Read past the end of the file?
    if (position >= fsize) {
        passedEOF = true;
        return 0;
    }

	// Make sure the ChunkSize is valid
	if (size <= 0 || size > fsize)
		chunkSize = fsize - position;

	// Try to open the file for binary reading
	FILE *fi = fopen (filename.c_str (), "rb");
	if (!fi) {
		if (log)
			log->Report ("ERROR! CISTREAM::BufferFromFile: Failed to open the file \"%s\"..\n", filename.c_str ());
		return 0;
	}

	fileName = filename;

	// Seek to the asked Position
	fseek (fi, position, SEEK_SET);

	// Read the file
	for (i=0; i<chunkSize; i++) {
        ret += fread (&b, sizeof (byte), 1, fi);

        if (caretPos >= (long) data.size ())
            data.push_back (b);
        else
            data.insert (data.begin () + caretPos, 1, b);

        caretPos++;
        TotalSize++;
	}

    // Append \0 just in case it's a text file
    b = 0;
    if (caretPos >= (long) data.size ())
        data.push_back (b);
    else
        data.insert (data.begin () + caretPos, 1, b);

	caretPos++;
	TotalSize++;

	fclose (fi);

	return ret;
}

//==============================================================================
// Prints into the buffer
//==============================================================================
long G3::CISTREAM::BufferPrint (const char *fmt, ...) {
	va_list    ap;
	char       buf [STREAM_PRINT_BUFFER];

	// Nothing to be written?
	if (fmt == NULL)
		return 0;

	// Clear the buffer. Just in case..
	memset (buf, 0, sizeof (char) * (STREAM_PRINT_BUFFER - 1));

	// Process arguments
	va_start (ap, fmt);
	vsprintf (buf, fmt, ap);
	va_end (ap);

	buf [STREAM_PRINT_BUFFER - 1] = 0;

    // Buffer the result
	return Buffer ((byte *) buf, strlen (buf));
}

//==============================================================================
// Gets the next biggest chunk from the stream
//==============================================================================
G3::STREAM::CHUNK G3::CISTREAM::GetChunk (long maxSize) {
    CHUNK chunk;

    if (caretPos >= (long) data.size ()) {
        passedEOF = true;
        return chunk;
    }

    if (maxSize < (long) data.size () && maxSize > 0) {
        chunk.chunkSize = maxSize;
    } else {
		chunk.chunkSize = data.size () - caretPos;
		passedEOF = true;
	}

    chunk.chunkPointer = &data.at (caretPos);
    caretPos += chunk.chunkSize;

    return chunk;
}

//==============================================================================
// Returns the stream data pointer
//==============================================================================
byte *G3::CISTREAM::GetDataPointer () {
	return &data.at (0);
}

void G3::CISTREAM::DumpToLog () {
    log->Report ("Dumping the contents of \"%s\" stream:\n", fileName.c_str ());

    for (long i=0; i<(long)data.size (); i++) {
        log->Report ("0x%X (%c)", data.at (i), data.at (i));
    }
}
